package tools

import java.io.{File, FileInputStream}
import java.net.URLEncoder
import java.security.{KeyStore, MessageDigest}
import java.util
import java.util.{Formatter, UUID}

import scala.collection.JavaConversions.seqAsJavaList
import com.typesafe.config.ConfigFactory
import common.{Tool, JedisTool}
import org.apache.http.HttpEntity
import org.apache.http.client.CookieStore
import org.apache.http.client.entity.UrlEncodedFormEntity
import org.apache.http.client.methods.{HttpPost, HttpGet}
import org.apache.http.client.protocol.HttpClientContext
import org.apache.http.conn.ssl.{ DefaultHostnameVerifier, SSLConnectionSocketFactory}
import org.apache.http.entity.{ContentType, ByteArrayEntity}
import org.apache.http.entity.mime.MultipartEntityBuilder
import org.apache.http.entity.mime.content.{FileBody, StringBody}
import org.apache.http.impl.client.HttpClients
import org.apache.http.message.BasicNameValuePair
import org.apache.http.ssl.SSLContexts
import org.apache.http.util.EntityUtils
import play.Logger

import scala.util.Random
import scala.util.parsing.json.JSON
import common.JedisCacheKeys._
import common.Tool._
import scala.collection.mutable

/**
 * Created by 林 on 14-4-11.
 */
class WeiXin(appid: String, secret: String,wxmchId:String="",payKey:String="") {
  lazy val conf = ConfigFactory.load()
  val code_msg = Map("-1" -> "系统繁忙",
    "0" -> "请求成功",
    "40001" -> "获取access_token时AppSecret错误，或者access_token无效",
    "40002" -> "不合法的凭证类型",
    "40003" -> "不合法的OpenID",
    "40004" -> "不合法的媒体文件类型",
    "40005" -> "不合法的文件类型",
    "40006" -> "不合法的文件大小",
    "40007" -> "不合法的媒体文件id",
    "40008" -> "不合法的消息类型",
    "40009" -> "不合法的图片文件大小",
    "40010" -> "不合法的语音文件大小",
    "40011" -> "不合法的视频文件大小",
    "40012" -> "不合法的缩略图文件大小",
    "40013" -> "不合法的APPID",
    "40014" -> "不合法的access_token",
    "40015" -> "不合法的菜单类型",
    "40016" -> "不合法的按钮个数",
    "40017" -> "不合法的按钮个数",
    "40018" -> "不合法的按钮名字长度",
    "40019" -> "不合法的按钮KEY长度",
    "40020" -> "不合法的按钮URL长度",
    "40021" -> "不合法的菜单版本号",
    "40022" -> "不合法的子菜单级数",
    "40023" -> "不合法的子菜单按钮个数",
    "40024" -> "不合法的子菜单按钮类型",
    "40025" -> "不合法的子菜单按钮名字长度",
    "40026" -> "不合法的子菜单按钮KEY长度",
    "40027" -> "不合法的子菜单按钮URL长度",
    "40028" -> "不合法的自定义菜单使用用户",
    "40029" -> "不合法的oauth_code",
    "40030" -> "不合法的refresh_token",
    "40031" -> "不合法的openid列表",
    "40032" -> "不合法的openid列表长度",
    "40033" -> "不合法的请求字符",
    "40035" -> "不合法的参数",
    "40038" -> "不合法的请求格式",
    "40039" -> "不合法的URL长度",
    "40050" -> "不合法的分组id",
    "40051" -> "分组名字不合法",
    "41001" -> "缺少access_token参数",
    "41002" -> "缺少appid参数",
    "41003" -> "缺少refresh_token参数",
    "41004" -> "缺少secret参数",
    "41005" -> "缺少多媒体文件数据",
    "41006" -> "缺少media_id参数",
    "41007" -> "缺少子菜单数据",
    "41008" -> "缺少oauth code",
    "41009" -> "缺少openid",
    "42001" -> "access_token超时",
    "42002" -> "refresh_token超时",
    "42003" -> "oauth_code超时",
    "43001" -> "需要GET请求",
    "43002" -> "需要POST请求",
    "43003" -> "需要HTTPS请求",
    "43004" -> "需要接收者关注",
    "43005" -> "需要好友关系",
    "44001" -> "多媒体文件为空",
    "44002" -> "POST的数据包为空",
    "44003" -> "图文消息内容为空",
    "44004" -> "文本消息内容为空",
    "45001" -> "多媒体文件大小超过限制",
    "45002" -> "消息内容超过限制",
    "45003" -> "标题字段超过限制",
    "45004" -> "描述字段超过限制",
    "45005" -> "链接字段超过限制",
    "45006" -> "图片链接字段超过限制",
    "45007" -> "语音播放时间超过限制",
    "45008" -> "图文消息超过限制",
    "45009" -> "接口调用超过限制",
    "45010" -> "创建菜单个数超过限制",
    "45015" -> "回复时间超过限制",
    "45016" -> "系统分组，不允许修改",
    "45017" -> "分组名字过长",
    "45018" -> "分组数量超过上限",
    "46001" -> "不存在媒体数据",
    "46002" -> "不存在的菜单版本",
    "46003" -> "不存在的菜单数据",
    "46004" -> "不存在的用户",
    "47001" -> "解析JSON/XML内容错误",
    "48001" -> "api功能未授权",
    "50001" -> "用户未授权该api"
  )
  private var last_time: Long = 0
  private var token: String = ""
  private var expires = 0

  //主动发消息给用户
  def sendMsg(id: String, content: String): Boolean = {
    token = getToken
    val data = s"""{"touser":"${id}","msgtype":"text","text":{"content":"${content}"}}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=$token", entity = entity)._2
    val code = dealResult(result_data)
    if ((40001 :: 40014 :: 42001 :: Nil).exists(code == _)) {
      return sendMsg(id, content)
    }
    //    else if (code!=0 && (code !=45015))
    //      sendMail('微信邮件无法正常发出',result_data)
    return dealResult(result_data) == 0
  }

  //主动发消息给用户
  def sendImage(id: String, image: String): Boolean = {
    token = getToken
    val data = s"""{"touser":"${id}","msgtype":"image","image":{"media_id":"${image}"}}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=$token", entity = entity)._2
    val code = dealResult(result_data)
    if ((40001 :: 40014 :: 42001 :: Nil).exists(code == _)) {
      return sendImage(id, image)
    }
    return dealResult(result_data) == 0
  }

  //主动发图文消息给用户
  def sendNews(id: String, datas:List[Map[String,String]]): Boolean = {
    token = getToken
    val data = s"""{"touser":"${id}","msgtype":"news","news":{"articles":${datas.toJson}}}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=$token", entity = entity)._2
    val code = dealResult(result_data)
    if ((40001 :: 40014 :: 42001 :: Nil).exists(code == _)) {
      return sendNews(id, datas)
    }
    //    else if (code!=0 && (code !=45015))
    //      sendMail('微信邮件无法正常发出',result_data)
    return dealResult(result_data) == 0
  }

  //创建菜单
  def createMenu() = {
    val token = getToken()
    val data =
      s"""{"button":[{"name":"交投置地","sub_button":[{"name":"项目概况","type":"media_id","media_id":"bqDMcwEaV5cPJWFx0Cfmv4kPkpIa8kmRhi6Pb6OOmAk"},{"name":"户型品鉴","type":"media_id","media_id":"bqDMcwEaV5cPJWFx0CfmvxX6bOIbIWVqS-A2JpXZaNY"},{"name":"企业背景
        |","type":"media_id","media_id":"bqDMcwEaV5cPJWFx0Cfmv-uuD_nEf3Yxo5Ip_LMKyIg"}]},
        |{"name":"近期活动","type":"media_id","media_id":"bqDMcwEaV5cPJWFx0Cfmv5i_u-mBgN5j_mD_fl9F2PE"}}""".stripMargin
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${token}", entity = entity)._2
    if (dealResult(result_data) != 0)
      throw new Exception(result_data)
    dealResult(result_data)
  }

  def moveGroup(uid: String, id: Int) {
    val token = getToken()
    val data = s"""{"openid":"${uid}","to_groupid":${id}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=${token}", entity = entity)._2
    if (dealResult(result_data) != 0)
      throw new Exception(result_data)
    dealResult(result_data)
  }
  def createGroup(name: String)={
    val token = getToken()
    val data = s"""{"group":{"name":"$name"}}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/groups/create?access_token=${token}", entity = entity)._2
    val om=Map[String, Any]().toBean(result_data)
    println(om)
    om("group").asInstanceOf[Map[String,Any]]("id").toString
  }

  def userInfo(uid: String):Map[String,String] = {
    val token = getToken
    val result_data = NetTool.HttpGet(s"https://api.weixin.qq.com/cgi-bin/user/info?access_token=${token}&openid=${uid}&lang=zh_CN")._2
    if (result_data != null) {
      JSON.parseFull(result_data).getOrElse(Map("" -> "")).asInstanceOf[Map[String,String]]
    } else Map("" -> "")
  }

  def users:List[Map[String,String]] = {
    val token = getToken
    val result_data = NetTool.HttpGet(s"https://api.weixin.qq.com/cgi-bin/user/get?access_token=${token}&next_openid=")._2
    Map[String,Any]().toBean(result_data)("data").asInstanceOf[Map[String,Any]]("openid").asInstanceOf[List[Map[String,String]]]
  }

  def remark(uid: String, name: String) = {
    val token = getToken()
    val data = s"""{"openid":"${uid}","remark":"${name}"}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=${token}", entity = entity)._2
    if (dealResult(result_data) != 0)
      throw new Exception(result_data)
    dealResult(result_data)
  }

  //处理返回结果
  def dealResult(data: String): Int = {
    if (data != null) {
      JSON.parseFull(data) map {
        case m: Map[_,_] =>
          val code = m.asInstanceOf[Map[String,Any]]("errcode").toString.toDouble.toInt
          if ((42001 :: 41001 :: 40014 :: 40001 :: Nil).exists(code == _)) {
            JedisTool.useJedis(_.del(WEIXIN_JS_TOKEN+appid))
            getToken
          }
          return code
      }
    }
    return -1
  }

  //获取access_token
  def getToken() = {
    if (conf.getString("debug") == "true") {
          Logger.info("server token")
          cacheMethod("weixin_server_token",60) {
            val url=conf.getString("serverIp")+"?app="+appid
            Logger.info(url)
            NetTool.HttpGet(url)._2
          }
    } else {
      JedisTool.useJedis {v=>
        (WEIXIN_TOKEN+appid).synchronized {
          v.get(WEIXIN_TOKEN+appid).getOrElse {
            val strdata = NetTool.HttpGet(s"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}")
            JSON.parseFull(strdata._2) map {
              case m: Map[_, _] =>
                token = m.asInstanceOf[Map[String, Any]]("access_token").toString
                JedisTool.useJedis(_.set(WEIXIN_TOKEN+appid, token))
                JedisTool.useJedis(_.expire(WEIXIN_TOKEN+appid, m.asInstanceOf[Map[String, Any]]("expires_in").toString.toDouble.toInt - 10))
            }
            token
          }
        }
      }
    }
  }

  //创建绑定二维码,100秒的失效时间
  def RealQrcode(code: String,limit:Boolean=false):String = {
    val data = if (!limit)
      s"""{"expire_seconds": 100, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id":${code}}}"""
    else
      s"""{"action_name": "QR_LIMIT_SCENE", "action_info": {"scene": {"scene_id":${code}}}"""
    val token = getToken()
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=$token", entity = entity)._2
    Logger.info(result_data)
    JSON.parseFull(result_data) map {
      case m: Map[_, _] =>
        if (m.asInstanceOf[Map[String,Any]].getOrElse("errcode", null) != null) {
          last_time = 0
          getToken()
          return RealQrcode(code)
        } else {
          return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + m.asInstanceOf[Map[String,Any]]("ticket").toString
        }
    } orNull
  }

  def Qrcode(uid: String,limit:Boolean=false): (String, Int) = {
    val code = RandCode(uid)
    (RealQrcode(code,limit),code)
  }

  //创建绑定随机数,100秒的失效时间
  def RandCode(data: String, t: Int = 100): Int = {
    val code = Random.nextInt(1000000)
    val key = "wx_rand_code" + code
    if (JedisTool.useJedis(_.get(key)).isDefined)
      return RandCode(data, t)
    JedisTool.useJedis(_.set(key, data))
    JedisTool.useJedis(_.expire(key, t))
    return code
  }
  def getJsToken() = {
    if (conf.getString("debug") == "true") {
      Logger.info("server js token")
      cacheMethod("weixin_js_server_token",60) {
        val url="http://139.129.22.23/api/system/weixin/jstoken"+"?app="+appid
        Logger.info(url)
        NetTool.HttpGet(url)._2
      }
    } else {
      JedisTool.useJedis {v=>
        (WEIXIN_JS_TOKEN+appid).synchronized {
          v.get(WEIXIN_JS_TOKEN+appid).getOrElse {
            var ticket=""
            val token = getToken()
            val strdata = NetTool.HttpGet(s"https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=jsapi")
            JSON.parseFull(strdata._2) map {
              case m: Map[_,_] =>
                if(strdata._2.contains("expires_in")){
                  expires = m.asInstanceOf[Map[String,Any]]("expires_in").toString.toDouble.toInt - 10
                  ticket = m.asInstanceOf[Map[String,Any]]("ticket").toString
                  JedisTool.useJedis(_.set(WEIXIN_JS_TOKEN+appid, ticket))
                  JedisTool.useJedis(_.expire(WEIXIN_JS_TOKEN+appid, expires))
                }else{
                  dealResult(strdata._2)
                }
            }
            ticket
          }
        }
      }
    }
  }
  //js签名
  def JsSignature(url: String) = {
    sign(getJsToken, url)
  }
  //js签名
  def sign(jsapi_ticket: String, url: String) = {
    val nonce_str: String = randomStr(6)
    val timestamp: String = System.currentTimeMillis() / 1000 toString
    var signature: String = ""
    val string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "&timestamp=" + timestamp + "&url=" + url
    val crypt: MessageDigest = MessageDigest.getInstance("SHA-1")
    crypt.reset
    crypt.update(string1.getBytes("UTF-8"))
    signature = bytes2hex(crypt.digest)
    Map("appId"->appid, "url" -> url, "jsapi_ticket" -> jsapi_ticket, "nonceStr" -> nonce_str, "timestamp" -> timestamp, "signature" -> signature)
  }

  def templateMessage(uid: String, template: String,url:String,map:Map[String,Map[String,String]]) = {
    val token = getToken()
    val data = s"""{"touser":"${uid}","template_id":"${template}","url":"${url}","topcolor":"#FF0000","data":${map.toJson}}"""
    println(data)
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${token}", entity = entity)._2
    if (dealResult(result_data) != 0)
      throw new Exception(result_data)
    dealResult(result_data)
  }

  def rechargeWxPay(title:String,ordeId:String,amount:Int,ip:String,notifyUrl:String,openid:String)={
    val params=new mutable.HashMap[String,String]()
    params("appid")=appid
    params("mch_id")=wxmchId
    params("nonce_str")=Tool.randomStr(20)
    params("body")=title
    params("out_trade_no")=ordeId
    params("total_fee")=amount.toString
    params("spbill_create_ip")=ip
    params("notify_url")=notifyUrl
    params("trade_type")="JSAPI"
    params("openid")=openid
//    params("device_info")="WEB"
    params("sign")=(SignTool.sortKeyAsc(params.toMap,false)+"&key="+payKey).md5
    params
  }

  def checkSignWxPay(params:Map[String,String])={
    val sign=params("sign")
    (SignTool.sortKeyAsc(params.filter(_._1!="sign"),false)+"&key="+payKey).md5
  }

  def genWxHtmlPay(id:String)={
    val params=new mutable.HashMap[String,String]()
    params("appId")=appid
    params("timeStamp")=(System.currentTimeMillis()/1000).toString
    params("nonceStr")=Tool.randomStr(20)
    params("package")="prepay_id="+id
    params("signType")="MD5"
    params("paySign")=(SignTool.sortKeyAsc(params.toMap,false)+"&key="+payKey).md5
    params
  }

  def getAutoMenuUrl(url:String,state:String)={
    s"https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&response_type=code&scope=snsapi_base&redirect_uri=${URLEncoder.encode(url, "utf-8")}"+(if(isEmpty(state))""else "&state="+state)+"#wechat_redirect"
  }
  def getAutoMenuUrl(url:String)={
    s"https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&response_type=code&scope=snsapi_base&state=state&redirect_uri=${url}#wechat_redirect"
  }
  def getAutoMenuSuUrl(url:String)={
    s"https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&response_type=code&scope=snsapi_userinfo&state=state&redirect_uri=${url}#wechat_redirect"
  }
  def menus = {
    val token = getToken
    val data=NetTool.HttpGet(s"https://api.weixin.qq.com/cgi-bin/menu/get?access_token=${token}")._2
    JSON.parseFull(data).get
  }

  def shorturl(url: String) = {
    val token = getToken()
    val data = s"""{"long_url":"${url}","action":"long2short"}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data = NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/shorturl?access_token=${token}", entity = entity)._2
    if (dealResult(result_data) != 0)
      throw new Exception(result_data)
    JSON.parseFull(result_data).get.asInstanceOf[Map[String,Any]]("short_url")
  }
  def material(stype:String,offset:Int=0,count:Int=20) = {
    val token = getToken
    val data = s"""{"type":"${stype}","offset":$offset,"count":$count}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    val result_data=NetTool.HttpPost(s"https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=${token}", entity = entity)._2
    JSON.parseFull(result_data).get
  }

  def getmaterial(id:String) = {
    val token = getToken
    val data = s"""{"media_id":"${id}"}"""
    val entity = new ByteArrayEntity(data.getBytes("UTF-8"))
    NetTool.HttpPostBin(s"https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=${token}", entity = entity)._2
  }
  def downloadmaterial(id:String) = {
    val token = getToken
    NetTool.HttpGetBin(s"http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=${token}&media_id="+id)._2
  }

  /**
    * 微信红包区域
    */

  lazy val certHttpClient={
    val keyStore = KeyStore.getInstance("PKCS12")
    val instream = new FileInputStream(new File(conf.getString("paycert")))
    try {
      //指定PKCS12的密码(商户ID)
      keyStore.load(instream, wxmchId.toCharArray())
    } finally {
      instream.close()
    }
    val sslcontext = SSLContexts.custom()
      .loadKeyMaterial(keyStore, wxmchId.toCharArray()).build()
    val sslsf = new SSLConnectionSocketFactory(
      sslcontext,Array[String]("TLSv1"),null, new DefaultHostnameVerifier())
    HttpClients.custom().setSSLSocketFactory(sslsf).build()
  }

  def certHttpPost(url: String,data: Map[String, String] = null,entity:HttpEntity=null) = {
    val v=requestServer(url, "post", data,entity=entity,httpReTryCount = 0)
    EntityUtils.toString(v,"utf-8")
  }


  def createNameValueList(params: Map[String, String]) = {
    (for ((k, v) <- params)
      yield new BasicNameValuePair(k, v)).toList
  }
  def requestServer(url: String, method: String = "get",data: Map[String, String] = null,httpReTryCount:Int=5,entity:HttpEntity=null) = {
    val head=Map("User-Agent" -> ("	Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0"))
    val requestMethod = if (method == "get") new HttpGet(url) else new HttpPost(url)
    head.foreach(entry => requestMethod.setHeader(entry._1, entry._2))
    if (data != null) {
      requestMethod.asInstanceOf[HttpPost].setEntity(new UrlEncodedFormEntity(createNameValueList(data), "utf-8"))
    }
    if(entity!=null){
      requestMethod.asInstanceOf[HttpPost].setEntity(entity)
    }
    val client = certHttpClient
    val context = HttpClientContext.create()
    var content: HttpEntity = null
    reTry(httpReTryCount) {
      val response = client.execute(requestMethod,context)
      val status = response.getStatusLine().getStatusCode()
      content =response.getEntity()
      if (status != 200 && status != 302) {
        throw new Exception(status + ":" + EntityUtils.toString(content,"utf-8"))
      }
    }
    content
  }


  def redpackWxPay(title:String,sendName:String,actName:String,desc:String, ordeId:String,amount:Int,ip:String,openid:String)={
    val params=new mutable.HashMap[String,String]()
    params("wxappid")=appid
    params("mch_id")=wxmchId
    params("send_name")=sendName
    params("nonce_str")=Tool.randomStr(20)
    params("mch_billno")=ordeId
    params("total_num")="1"
    params("wishing")=title
    params("total_amount")=amount.toString
    params("client_ip")=ip
    params("act_name")=actName
    params("re_openid")=openid
    params("remark")=desc
    params("sign")=(SignTool.sortKeyAsc(params.toMap,false)+"&key="+payKey).md5
    params
  }



}